/*
 * Copyright 2002-2005 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.bingosoft.common.swato;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import net.bingosoft.common.BeanHelper;
import net.bingosoft.common.json.JSONArray;
import net.bingosoft.common.json.JSONConverter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Zhijie Chen (zigzag.chen@gmail.com)
 */
public class SwatoInvoker {
	private static Log log = LogFactory.getLog(SwatoInvoker.class);

	public Object invoke(JSONRequest jsonReq,HttpServletResponse resp,Object invokeTarget) throws Exception {
		if (invokeTarget == null) {
			throw new IllegalArgumentException("'invokeTarget' is required");
		}
		if (invokeTarget instanceof IRequestAware) {
			((IRequestAware) invokeTarget).setRequest(jsonReq);
		}
		if (invokeTarget instanceof ISessionAware) {
			((ISessionAware) invokeTarget).setSession(jsonReq.getSession());
		}
		Object invokerResult = null;
		// get the method name and params list
		String methodName = jsonReq.getMethodName();
		JSONArray params = jsonReq.getParams();
		if (BeanHelper.isNull(params)) {
			log.debug("invoke without Params");
			invokerResult = invokeTarget.getClass().getMethod(methodName, new Class[] {}).invoke(invokeTarget, new Object[] {});
		} else {
			log.debug("invoke with Params:" + invokeTarget + "." + methodName + "(" + params + ")");
			invokerResult = invokeWithParams(invokeTarget, methodName, params);
		}
		//clear request or session
		if (invokeTarget instanceof IRequestAware) {
			((IRequestAware) invokeTarget).setRequest(null);
		}
		if (invokeTarget instanceof ISessionAware) {
			((ISessionAware) invokeTarget).setSession(null);
		}
		log.debug("invoke result:" + invokerResult);
		return JSONConverter.toJSON(invokerResult,null);
	}

	/**
	 * @param serviceObject
	 * @param methodName
	 * @param params
	 * @return
	 * @throws NoSuchMethodException
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws Exception
	 * @throws Exception
	 */
	private Object invokeWithParams(Object serviceObject, String methodName, JSONArray params) throws Exception {
		Method[] allMethods = serviceObject.getClass().getMethods();
		List candidateMethods = filterMethodsByName(allMethods, methodName);
		Object invokeResult = null;
		List javaParams = null;

		Iterator iter = candidateMethods.iterator();
		while (iter.hasNext()) {
			Method method = (Method) iter.next();
			Class[] paramTypes = method.getParameterTypes();
			javaParams = getMatchedParams(paramTypes, params);
			if (javaParams != null && javaParams.size() == params.length()) {
				invokeResult = method.invoke(serviceObject, javaParams.toArray());
				return invokeResult;
			} else {
				continue;
			}
		}
		throw new IllegalArgumentException();
	}

	/**
	 * @param paramTypes
	 * @param params
	 * @param javaParams
	 * @return
	 */
	private List getMatchedParams(Class[] paramTypes, JSONArray params) throws Exception {
		List<Object> javaParams = new ArrayList<Object>();
		if (paramTypes.length != params.length()) {
			return null;
		} else {
			for (int i = 0; i < paramTypes.length; i++) {
				Class clazz = paramTypes[i];
				javaParams.add(JSONConverter.getTypedValue(clazz, params, i));

			}
		}
		return javaParams;
	}

	/**
	 * @param methods
	 * @param methodName
	 * @throws NoSuchMethodException
	 */
	private List filterMethodsByName(Method[] methods, String methodName) throws NoSuchMethodException {
		List<Method> methodsWithTheSameName = new ArrayList<Method>();
		for (int i = 0; i < methods.length; i++) {
			Method method = methods[i];
			if (method.getName().equals(methodName)) {
				methodsWithTheSameName.add(method);
			}
		}
		if (methodsWithTheSameName.size() == 0) {
			throw new NoSuchMethodException(methodName);
		}
		return methodsWithTheSameName;
	}
}
